In computing terms, a program is a specific set of ordered operations for a computer to perform. With an elementary form of programming language known as machine language, a programmer familiar with machine language can peek and poke data into and out from computer memory, and perform other simple mathematical transformations on data. Over time, however, the desired range of functionality of computer programs has increased quite significantly making programming in machine language generally cumbersome. As a result, proxy programming languages capable of being compiled into machine language and capable of much higher levels of logic have evolved. Examples of such evolving languages include COBOL, FORTRAN, Basic, PASCAL, C, C++, LISP, VISUAL BASIC, C# and many others. Some programming languages tend to be better than others at performing some types of tasks, but in general, the later in time the programming language was introduced, the more complex functionality that the programming language possesses empowering the developer more and more over time. Additionally, class libraries containing methods, classes and types for certain tasks are available so that, for example, a developer coding mathematical equations need not derive and implement the sine function from scratch, but need merely include and refer to the mathematical library containing the sine function.
Further increasing the need for evolved software in today's computing environments is that software is being transported from computing device to computing device and across platforms more and more. Thus, developers are becoming interested in aspects of the software beyond bare bones standalone personal computer (PC) functionality. To further illustrate how programming languages continue to evolve, in one of the first descriptions of a computer program by John von Neumann in 1945, a program was defined as a one-at-a-time sequence of instructions that the computer follows. Typically, the program is put into a storage area accessible to the computer. The computer gets one instruction and performs it and then gets the next instruction. The storage area or memory can also contain the data on which the instruction operates. A program is also a special kind of “data” that tells how to operate on application or user data. While not incorrect for certain simple programs, the view is one based on the simplistic world of standalone computing and one focused on the functionality of the software program.
However, since that time, with the advent of parallel processing, complex computer programming languages, transmission of programs and data across networks, and cross platform computing, the techniques have grown to be considerably more complex, and capable of much more than the simple standalone instruction by instruction model once known.
For more general background, programs can be characterized as interactive or batch in terms of what drives them and how continuously they run. An interactive program receives data from an interactive user or possibly from another program that simulates an interactive user. A batch program runs and does its work, and then stops. Batch programs can be started by interactive users who request their interactive program to run the batch program. A command interpreter or a Web browser is an example of an interactive program. A program that computes and prints out a company payroll is an example of a batch program. Print jobs are also batch programs.
When one creates a program, one writes it using some kind of computer language and the collection(s) of language statements are the source program(s). One then compiles the source program, along with any utilized libraries, with a special program called a language compiler, and the result is called an object program (not to be confused with object-oriented programming). There are several synonyms for an object program, including object module, executable program and compiled program. The object program contains the string of 0s and 1s called machine language with which the logic processor works. The machine language of the computer is constructed by the language compiler with an understanding of the computer's logic architecture, including the set of possible computer instructions and the bit length of an instruction.
Other source programs, such as dynamic link libraries (DLL) are collections of small programs, any of which can be called when needed by a larger program that is running in the computer. The small program that lets the larger program communicate with a specific device such as a printer or scanner is often packaged as a DLL program (usually referred to as a DLL file). DLL files that support specific device operation are known as device drivers. DLL files are an example of files that may be compiled at run-time.
The advantage of DLL files is that, because they don't get loaded into random access memory (RAM) together with the main program, space is saved in RAM. When and if a DLL file is needed, then it is loaded and executed. For example, as long as a user of Microsoft Word® is editing a document, the printer DLL file does not need to be loaded into RAM. If the user decides to print the document, then the Word application causes the printer DLL file to be loaded into the execution space for execution.
A DLL file is often given a “.dll” file name suffix. DLL files are dynamically linked with the program that uses them during program execution rather than being compiled with the main program. The set of such files (or the DLL) is somewhat comparable to the library routines provided with programming languages such as FORTRAN, Basic, PASCAL, C, C++, C#, etc.
The above background illustrates (1) that computer programming needs can change quickly in a very short time along with the changing computing environments in which they are intended to operate and (2) that computing programming environments are considerably more complex than they once were. As computing environments become more and more complex, there is generally a greater need for uniformity of functionality across platforms, uniformity among programming language editors, uniformity among programming language compilers and run time aspects of programming. In short, as today's computer system architectures have quickly expanded to the limits of the Earth via global networks, the types of programming tasks that are possible and desirable has also expanded to new limits. For example, since a program may traverse hundreds, if not hundreds of thousands of computers, as a result of copying, downloading or other transmission of the source code, developed by a plurality of unknown developers, affiliated with one another or not, the version of a program, program module, software component, class definition and the like may not always be understood or capable of disambiguation at a given destination or location. As a program undergoes several modifications or transformations, even the developer herself may no longer remember which version of a class, or other programming element, is in effect. In particular, there has grown a need for programmers and computing devices to be able to uniformly embed versioning information about various programming elements into software program elements.
Versioning is the process of evolving a component over time in a compatible manner. A new version of a component is source compatible with a previous version if code that depends on the previous version can, when recompiled, work with the new version. In contrast, a new version of a component is binary compatible if a program, application, service or the like that depends on the old version can work with the new version without recompilation of the program, application or service.
Binary compatibility is an important feature for a new version of a class library because it makes it possible for programs that depend on the old version of the class library to work compatibility with the new version of the class library, without compilation. Binary compatibility is particularly desirable because bug fixes and new features may be deployed in a more expedient and efficient manner. In the case of certain bug fixes, such as security-related bugs, speed can be a critical factor.
Source compatibility is also an important feature. A developer dealing with source code that depends on one version of a component is much more likely to want to upgrade to a new version of this component if the existing source code can recompile without compile-time errors or run-time semantic problems.
While some programming languages allow for some limited versioning functionality, thus far, no programming language has presented versioning capabilities that are adequate for today's distributed computing environments in which, inter alia, maximum control may not be available for exercising over end user client bits to control the versions of such bits. Thus, generally, as code evolves over time, and as new client bits are introduced into various computing systems, conflicts and ambiguities as to the most recent version of software components, classes and the like may occur.
Overloading describes a common versioning scenario wherein two methods or classes possess the same name, but they have different signatures due to different parameters/arguments. If it is not possible to simply add a method to a base class because it may create an overloading problem due to a new signature, for instance, a base class could never realistically evolve and existing programming systems routinely fail to deal with this situation. Developers are forced to work around this by either (1) versioning in an incompatible manner, thus forcing component consumers to recompile in order to get the benefits of simple changes, such as bug fixes, or (2) trying to version in a compatible manner by choosing member names that are unlikely to conflict with names introduced by base classes.
The first solution is obviously unacceptable since it defeats the purpose of compatibility, and is the main problem presented by versioning. The second solution is problematic for at least two reasons. First, it is not possible, in general, to choose names that will not conflict. For a base class in a popular system such as the WINDOWS® operating system, there can literally be millions of derived classes. With millions of people independently coming up with names, conflicts are quite likely to occur.
Second, intentionally choosing names that are not likely to conflict negatively impacts programmer productivity since these names are inherently more difficult to remember. For example, if the natural name for a method is “PerformTask,” having the method instead be named “_PerformTask” negatively impacts the productivity of component consumers, who must remember whether to type “Perform Task,” the name the method would have had if it were present in the original version or “_PerformTask,” the intentionally mangled name.
Also, the widespread use of software components is a relatively new phenomenon. In today's computing environments, there is a lot of runtime functionality in the form of class libraries being supplied. Because of the large number of developers that consume these class libraries, there is a need for the (a) provision of compatible binary updates without breaking deployed code and (b) greater freedom to make as many changes as possible while maintaining binary compatibility.
As an early provider of component technology that allowed one component in binary form to depend on one or more other components in binary form, MICROSOFT® Corporation, the assignee of the present application, recognized early that binary-compatible versioning was an important feature. Thus, MICROSOFT®'s Object Linking and Embedding (OLE) software and VISUAL BASIC versions 4.0, 5.0 and 6.0 contained some infrastructure that enabled primitive forms of binary versioning. Despite these efforts, developers have continued to be hampered in these versioning scenarios. Thus, there is a continued need to address these versioning shortcomings in a more robust manner.
Despite such early attempts, most languages do not support binary compatibility at all, and many do little to facilitate source compatibility. In fact, some languages contain flaws that make it impossible, in general, to evolve a class over time without breaking at least some client code. OLE 2.0 and VISUAL BASIC 4.0/5.0/6.0 provided some limited support for binary versioning, none of which support provided an adequate solution to the versioning problems created by modern proliferation and evolution of class libraries and the like.
The C++ programming language deals with some overloading cases correctly. However, the C++ solution is not convenient for developers, and thus consumes additional time and computing resources. The C++ overload resolution rule only considers overloads defined at a single position in the inheritance hierarchy. If a developer wants overloads from both a base and a derived class to be considered, then the developer must add “forwarders” in the derived class that explicitly tell the compiler to consider the overloads in the base, thereby adding additional overhead to the program and making the programming task more complex.
Thus, it would be desirable to provide a robust system and methods for versioning software components in connection with a computer programming language. It would also be desirable to provide a versioning system that makes use of intelligent defaults. It would be further desirable to provide a versioning system that provides a vehicle for unambiguous specification of developer intent with respect to versioning of a software component. It would be further desirable to provide a versioning system that implements conflict resolution rules in connection with the versioning of software components in a programming language. It would be still further desirable to provide a versioning system that bounds names at run-time, but does not bound offsets at compile-time.